﻿#region Usings

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection; 

#endregion

namespace Gonte.Reflection
{
    /// <summary>
    /// Provides a fast access to the properties of a type
    /// </summary>
    public class TypeAccessor
    {
        #region Constructors

        public static TypeAccessor GetTypeAccessor(Type type)
        {
            if (!_typeAccessors.ContainsKey(type))
            {
                TypeAccessor typeAccessor = new TypeAccessor(type);

                lock (((ICollection)_typeAccessors).SyncRoot)
                {
                    _typeAccessors.Add(type, typeAccessor);
                }

                return typeAccessor;
            }

            return _typeAccessors[type];
        }

        private TypeAccessor(Type type)
        {
            foreach (PropertyInfo propertyInfo in type.GetProperties())
            {
                PropertyAccessor propertyAccessor = new PropertyAccessor(propertyInfo);
                _propertyAccessors.Add(propertyInfo.Name, propertyAccessor);
            }
        }

        #endregion

        #region Methods

        public bool Contains(string propertyName)
        {
            return _propertyAccessors.ContainsKey(propertyName);
        }

        public void SetValue(object @object, string propertyName, object value)
        {
            PropertyAccessor propertyAccessor = _propertyAccessors[propertyName];

            ExceptionThrower.ThrowWhen(
                propertyAccessor.CanSet == false,
                string.Format("Can not set the value of property: '{0}'. Verify that the declaring type is not a value type (such as struct)",
                    propertyAccessor.PropertyName));

            propertyAccessor.SetValue(@object, value);
        }

        public object GetValue(object @object, string propertyName)
        {
            PropertyAccessor propertyAccessor = _propertyAccessors[propertyName];

            ExceptionThrower.ThrowWhen(
                propertyAccessor.CanGet == false,
                string.Format("Can not get the value of property: '{0}'",
                    propertyAccessor.PropertyName));

            return propertyAccessor.GetValue(@object);
        }

        #endregion

        #region Properties

        public bool IsPrimitive(string propertyName)
        {
            return _propertyAccessors[propertyName].IsPrimitive;
        }

        public ICollection<string> PropertyNames
        {
            get { return _propertyAccessors.Keys; }
        }

        public Type GetItemType(string propertyName)
        {
            return CollectionHelper.GetItemType(_propertyAccessors[propertyName].Type);
        }

        #endregion

        #region Fields

        private Dictionary<string, PropertyAccessor> _propertyAccessors = new Dictionary<string, PropertyAccessor>( /* StringComparer.OrdinalIgnoreCase */);

        private static Dictionary<Type, TypeAccessor> _typeAccessors = new Dictionary<Type, TypeAccessor>(); // Caches the loaded type accessors 

        #endregion
    }
}
